package cn.com.open.itembank.service.impl;

import cn.com.open.itembank.common.Function;
import cn.com.open.itembank.common.GlobalConst;
import cn.com.open.itembank.common.enums.*;
import cn.com.open.itembank.dto.*;
import cn.com.open.itembank.entity.*;
import cn.com.open.itembank.mapper.RulesMapper;
import cn.com.open.itembank.service.*;
import cn.com.open.itembank.utils.DateUtil;
import cn.com.open.itembank.utils.StringUtil;
import com.alibaba.fastjson.JSONObject;
import org.apache.commons.collections.map.HashedMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.UnsupportedEncodingException;
import java.math.BigDecimal;
import java.util.*;

/*
 * The type Rules service.
*/

/**
 * The type Rules service.
 */
@Service
public class RulesServiceImpl implements RulesService {

    @Autowired
    private RulesMapper rulesMapper;
    @Autowired
    private ItemBankRemoteService itemBankService;
    @Autowired
    private QtiRemoteService qtiService;
    @Autowired
    private ItemRemoteService itemService;
    @Autowired
    private QuestionCategoryRemoteService questionCategoryService;
    @Autowired
    private QuestionTypeRemoteService questionTypeService;
    @Autowired
    private AsyncTask asyncTask;
    @Autowired
    private GenerateCreateTestPaper generateCreateTestPaperUtil;


    private static String itembankIdNotExist = "题库不存在";
    private static String noPermission = "无权操作策略";
    private static String incompleteData = "参数不完整";

    //查询列表
    private String rows = "rows";
    //符合条件的总条数
    private String counts = "count";

    //region 获取策略列表

    /**
     * 获取策略列表
     *
     * @param appKey     appkey
     * @param rulesModel 策略查询模型
     * @return the return model
     */


    @Override
    public ReturnModel getRulesList(String appKey, RulesSearchModel rulesModel) {

        //检验方法（验证权限）
        ReturnModel model = basicCheckForItemBank(appKey, rulesModel.getItembankId());

        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        //获取符合条件的总条数(做分页)
        int count = rulesMapper.getListCount(rulesModel);

        //获取列表
        List<Rules> rulesList = rulesMapper.getRules(rulesModel);

        //转json对象
        JSONObject json = new JSONObject();
        json.put(rows, rulesList);
        json.put(counts, count);

        //把json放进model中返回
        model.setData(json);
        model.setCode(GlobalConst.SUCCESS);
        return model;
    }
    //endregion

    //region 策略删除

    /**
     * 策略删除
     *
     * @param appKey     appkey
     * @param itemBankId 题库id
     * @param id         策略id
     * @return
     */

    @Override
    public ReturnModel removeRules(String appKey, String itemBankId, String id) {
        //检测的私有方法
        ReturnModel model = basicCheck(appKey, itemBankId, id);

        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        //就解除试题与策略的关系
        rulesMapper.deleteQuestionRulesRelation(id);

        //删除策略
        rulesMapper.deleteRules(id, itemBankId);

        model.setCode(GlobalConst.SUCCESS);
        model.setMessage("删除成功");
        return model;
    }
    //endregion

    //region 修改策略状态

    /**
     * 修改策略状态
     *
     * @param appKey     appKey
     * @param itemBankId 题库id
     * @param id         策略id
     * @return
     */

    @Override
    public ReturnModel updateRulesStatus(String appKey, String itemBankId, String id) {

        //检验权限
        ReturnModel model = basicCheck(appKey, itemBankId, id);

        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        //获取试卷信息
        Rules rules = (Rules) model.getData();

        //给状态赋值
        int newState = (rules.getStatus() == SysState.STOP.getIndex()) ? SysState.START.getIndex() : SysState.STOP.getIndex();

        //修改
        rulesMapper.updateStatus(id, newState);

        model.setCode(GlobalConst.SUCCESS);
        model.setMessage("修改成功");
        return model;
    }

    //endregion

    //region 添加策略

    /**
     * 添加策略
     * 注意：大题的难度抽题数量中不包含必选题的数量
     *
     * @param appKey         appKey
     * @param itemBankId     题库id
     * @param ruleInputModel 策略输入模型ruleInputModel
     * @return
     */

    @Override
    public ReturnModel addRule(String appKey, String itemBankId, RuleInputModel ruleInputModel) {
        //设置策略id
        String ruleId = UUID.randomUUID().toString();
        ruleInputModel.setRuleID(ruleId);

        //检验权限
        ReturnModel model = basicCheckForItemBank(appKey, itemBankId);
        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        //验证策略装载
        ReturnModel returnModel = checkRuleInputModelForEdit(ruleInputModel);
        if (returnModel.getCode() != GlobalConst.SUCCESS) {
            return returnModel;
        }

        RuleSaveModel saveModel = (RuleSaveModel) returnModel.getData();

        //取到ruleInputModel里的值，放到Rules中
        Rules rules = getInsertRules(ruleInputModel);

        //得到qti xml
        String contentXml = qtiService.getFullQtiXml(saveModel);

        //保存rulecontent
        rulesMapper.addRuleContent(rules.getRuleID(), contentXml);
        //添加策略
        rulesMapper.addRules(rules);

        model.setData(ruleId);
        model.setCode(GlobalConst.SUCCESS);
        model.setMessage("添加成功");

        return model;
    }
    //endregion

    //region 生成策略模型（做添加策略用的）

    /**
     * 生成策略模型（做添加策略用的）.
     *
     * @param ruleInputModel the rule input model
     * @return the rules
     */

    private Rules getInsertRules(RuleInputModel ruleInputModel) {

        Rules rules = new Rules();

        //取到ruleInputModel里的值，放到Rules中
        rules.setRuleID(ruleInputModel.getRuleID());
        rules.setOrganizationID(ruleInputModel.getOrgId());
        rules.setCourseID(ruleInputModel.getItemBankID());
        rules.setRuleName(ruleInputModel.getRuleName());
        rules.setScore(ruleInputModel.getTotalScore());
        rules.setTimeLimited(ruleInputModel.isTimeLimited() ? 1 : 0);
        if (ruleInputModel.getCreatorId() != null) {
            rules.setCreater(ruleInputModel.getCreatorId());
        } else {
            rules.setCreater("");
        }
        if (ruleInputModel.getCreateTime() != null) {
            rules.setCreateTime(ruleInputModel.getCreateTime());
        } else {
            rules.setCreateTime(DateUtil.toShowString(new Date()));
        }
        rules.setRuleVersion(1);
        rules.setIsCheck(YesOrNo.NO.getIndex());
        rules.setStatus(SysState.START.getIndex());
        rules.setDisplayCount("0");
        rules.setRuleType(1);
        rules.setScale(getScala(ruleInputModel));

        return rules;
    }
    //endregion

    //region 修改策略

    /**
     * 修改策略
     *
     * @param appKey         appKey
     * @param itemBankId     题库id
     * @param ruleInputModel 策略输入模型ruleInputModel
     * @return
     */

    @Override
    public ReturnModel updateRule(String appKey, String itemBankId, RuleInputModel ruleInputModel) {
        //检验权限
        ReturnModel model = basicCheck(appKey, itemBankId, ruleInputModel.getRuleID());
        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        //验证策略装载
        ReturnModel returnModel = checkRuleInputModelForEdit(ruleInputModel);
        if (returnModel.getCode() != GlobalConst.SUCCESS) {
            return returnModel;
        }

        Rules rules = (Rules) model.getData();
        RuleSaveModel saveModel = (RuleSaveModel) returnModel.getData();

        //取到ruleInputModel里的值，放到Rules中
        Rules newRules = getUpdateRules(ruleInputModel, rules);

        //得到qti xml
        String contentXml = qtiService.getFullQtiXml(saveModel);

        //修改RuleContent
        rulesMapper.updateRuleContent(ruleInputModel.getRuleID(), contentXml);
        //修改策略
        rulesMapper.updateRules(newRules);

        model.setCode(GlobalConst.SUCCESS);
        model.setMessage("修改成功");

        return model;
    }
    //endregion

    //region 生成策略模型（做修改策略用的）

    /**
     * 生成策略模型（做修改策略用的）.
     *
     * @param ruleInputModel the rule input model
     * @return the rules
     */

    private Rules getUpdateRules(RuleInputModel ruleInputModel, Rules dbRule) {
        Rules rules = new Rules();

        //取到ruleInputModel里的值，放到Rules中
        rules.setRuleID(dbRule.getRuleID());
        rules.setOrganizationID(ruleInputModel.getOrgId());
        rules.setRuleName(ruleInputModel.getRuleName());
        rules.setScore(ruleInputModel.getTotalScore());
        rules.setTimeLimited(ruleInputModel.isTimeLimited() ? 1 : 0);
        rules.setRuleVersion(new Integer(dbRule.getRuleVersion().intValue() + 1));
        rules.setLastUpdateTime(DateUtil.toShowString(new Date()));
        rules.setModifyTime(DateUtil.toShowString(new Date()));
        rules.setModifier(ruleInputModel.getModifierId());
        rules.setScale(getScala(ruleInputModel));

        return rules;
    }
    //endregion

    //region 获取策略信息

    /**
     * 获取策略信息
     *
     * @param appKey     appkey
     * @param itemBankId 题库ID
     * @param ruleID     策略ID
     * @return the return model
     */

    @Override
    public ReturnModel getRuleInfo(String appKey, String itemBankId, String ruleID) {

        //检测的私有方法
        ReturnModel model = basicCheck(appKey, itemBankId, ruleID);
        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        Rules rules = (Rules) model.getData();

        RuleInputModel ruleInputModel = getRuleInputModel(rules);

        //返回
        model.setCode(GlobalConst.SUCCESS);
        model.setData(ruleInputModel);
        return model;
    }

    //endregion

    //region同步组卷

    /**
     * 同步组卷
     *
     * @param appKey     appkey
     * @param itemBankId 题库id
     * @param ruleID     策略ruleID
     * @param paperCount 试卷数量
     * @param paperName  试卷名称
     * @return
     */
    @Override
    public ReturnModel createTestPaper(String appKey, String itemBankId, String ruleID, int paperCount, String paperName) {

        //检测的私有方法
        ReturnModel model = basicCheck(appKey, itemBankId, ruleID);
        if (model.getCode() != GlobalConst.SUCCESS){
            return model;
        }
        if (paperCount == 0 || StringUtil.isEmpty(paperName)) {
            model.setCode(GlobalConst.INCOMPLETE_DATA);
            model.setMessage(incompleteData);
            return model;
        }

        Rules rules = (Rules) model.getData();
        RuleContent rulecontent = rulesMapper.getRuleContentById(ruleID);
        if (rules == null || rulecontent == null) {
            model.setCode(GlobalConst.ID_NOT_EXIST);
            model.setMessage("策略id不存在");
            return model;
        }

        //得到构造策略模型实体
        RuleSaveModel ruleSaveModel = qtiService.getObjectByXml(rulecontent.getContentXml());
        ruleSaveModel.setItemBankID(rules.getCourseID());

        //模型传值
        AsyncRule asyncRule = mutatorAsyncRule(ruleSaveModel, appKey, itemBankId, ruleID, paperCount, paperName, "");

        model = generateCreateTestPaperUtil.generateCreateTestPaper(asyncRule);
        return model;
    }

    //endregion

    //region 组卷赋值私有方法

    /**
     * 组卷赋值私有方法
     * @param appKey     appkey
     * @param itemBankId 题库id
     * @param ruleID     策略ruleID
     * @param paperCount 试卷数量
     * @param paperName  试卷名称
     * @param backUrl    回调路径
     * @return
     */
    private AsyncRule mutatorAsyncRule(RuleSaveModel ruleSaveModel, String appKey, String itemBankId, String ruleID, int paperCount, String paperName, String backUrl) {

        //校验策略
        ReturnModel checkRuleModel = checkRule(ruleSaveModel);
        //模型传值
        AsyncRule asyncRule = new AsyncRule();
        asyncRule.setCheckRuleModel(checkRuleModel);
        asyncRule.setAppKey(appKey);
        asyncRule.setItemBankId(itemBankId);
        asyncRule.setPaperCount(paperCount);
        asyncRule.setPaperName(paperName);
        asyncRule.setRuleID(ruleID);
        if (StringUtil.isEmpty(backUrl)) {
            asyncRule.setUrl(backUrl);
        }
        asyncRule.setRuleSaveModel(ruleSaveModel);

        return asyncRule;
    }

    //endregion

    //region异步组卷

    /**
     * 异步组卷
     *
     * @param appKey     appkey
     * @param itemBankId 题库id
     * @param ruleID     策略ruleID
     * @param paperCount 试卷数量
     * @param paperName  试卷名称
     * @param backUrl    回调路径
     * @return
     */
    @Override
    public ReturnModel asyncGenerateCreateTestPaper(String appKey, String itemBankId, String ruleID, int paperCount, String paperName, String backUrl) throws UnsupportedEncodingException {

        //检测的私有方法
        ReturnModel model = basicCheck(appKey, itemBankId, ruleID);
        if (model.getCode() != GlobalConst.SUCCESS){
            return model;
        }
        if (paperCount == 0 || StringUtil.isEmpty(paperName)) {
            model.setCode(GlobalConst.INCOMPLETE_DATA);
            model.setMessage(incompleteData);
            return model;
        }
        Rules rules = rulesMapper.getRulesById(ruleID);
        RuleContent rulecontent = rulesMapper.getRuleContentById(ruleID);
        if (rules == null || rulecontent == null) {
            model.setCode(GlobalConst.ID_NOT_EXIST);
            model.setMessage("策略id不存在");
            return model;
        }
        //得到构造策略模型实体
        RuleSaveModel ruleSaveModel = qtiService.getObjectByXml(rulecontent.getContentXml());
        ruleSaveModel.setItemBankID(rules.getCourseID());

        //模型传值
        AsyncRule asyncRule = mutatorAsyncRule(ruleSaveModel, appKey, itemBankId, ruleID, paperCount, paperName, backUrl);
        //异步组卷
        asyncTask.asyncGenerateCreateTestPaper(asyncRule);

        model.setCode(GlobalConst.SUCCESS);
        model.setData(null);
        return model;
    }

    //endregion

    //region 私有方法

    //region 根据策略ID获取到对应的Rules 和 RuleContent

    /**
     * 根据策略ID获取到对应的Rules 和 RuleContent
     *
     * @param rules the ruleID
     * @return the ruleInputModel
     */

    private RuleInputModel getRuleInputModel(Rules rules) {
        //返回的策略输入模型
        RuleInputModel inputModel = new RuleInputModel();

        //获取ruleContent表中的信息
        RuleContent ruleContent = rulesMapper.getRuleContentById(rules.getRuleID());

        //调用接口将MXL文件转换成策略保存模型
        RuleSaveModel saveModel = qtiService.getObjectByXml(ruleContent.getContentXml());

        //给策略输入模型赋值(来自策略信息表)
        inputModel.setRuleName(rules.getRuleName());
        inputModel.setItemBankID(rules.getCourseID());
        inputModel.setOrgId(rules.getOrganizationID());
        inputModel.setTimeLimited(rules.getTimeLimited() == 1);
        inputModel.setCreateTime(rules.getCreateTime());
        inputModel.setCreatorId(rules.getCreater());
        inputModel.setModifierId(rules.getModifier());
        inputModel.setModifyTime(rules.getModifyTime());

        //给策略输入模型赋值(来自策略保存模型)
        inputModel.setRuleID(saveModel.getRuleID());
        inputModel.setTotalScore(saveModel.getTotalScore());

        //part
        getInputPartFromSave(inputModel, saveModel);

        //应用类别占比
        Map<String, BigDecimal> categories = new HashedMap();

        String[] scales = rules.getScale().split(",");
        for (int i = 0; i < saveModel.getQuestionCategories().size(); i++) {
            BigDecimal scala = BigDecimal.ZERO;
            if (i < scales.length) {
                scala = new BigDecimal(scales[i]);
            }
            categories.put(saveModel.getQuestionCategories().get(i), scala);
        }

        inputModel.setQuestionCategories(categories);

        return inputModel;
    }
    //endregion

    //region 根据策略保存模型得到策略输入模型

    /**
     * 根据策略保存模型得到策略输入模型.
     *
     * @param inputModel the return model
     * @param saveModel  the save model
     */

    private void getInputPartFromSave(RuleInputModel inputModel, RuleSaveModel saveModel) {
        for (RulePartSaveModel partSaveModel : saveModel.getParts()) {
            RulePartInputModel partInputModel = partSaveModel.copyToInput();

            for (RuleSectionSaveModel sectionSaveModel : partSaveModel.getSections()) {
                RuleSectionInputModel sectionInputModel = sectionSaveModel.copyToInput();

                Map<String, RuleDifficultyModel> diffModelSet = new LinkedHashMap<>();
                Map<String, Float> diffCountSet = new LinkedHashMap();

                for (RuleDifficultySaveModel difficultySaveModel : sectionSaveModel.getDifficultyList()) {
                    RuleDifficultyModel difficultyModel = difficultySaveModel.copy();
                    String setKey = String.format("%s-%d", difficultySaveModel.getType(), difficultySaveModel.getSubItemCount());

                    if (diffCountSet.containsKey(setKey)) {
                        //存在同难度同子试题个数，表示是存在类别区分
                        diffCountSet.put(setKey, diffCountSet.get(setKey) + difficultySaveModel.getRealCount());
                    } else {
                        diffCountSet.put(setKey, difficultySaveModel.getRealCount());
                        diffModelSet.put(setKey, difficultyModel);
                    }
                }

                for (Map.Entry<String, Float> map : diffCountSet.entrySet()) {
                    diffModelSet.get(map.getKey()).setCount(map.getValue().intValue());
                }

                sectionInputModel.getDifficultyList().addAll(diffModelSet.values());

                partInputModel.getSections().add(sectionInputModel);
            }

            inputModel.getParts().add(partInputModel);
        }
    }
    //endregion

    //region 检查策略模型中是否存在重复的题型

    /**
     * 检查策略模型中是否存在重复的题型.
     *
     * @param ruleInputModel the rule input model
     * @return the boolean
     */

    private boolean checkSectionUniqueness(RuleInputModel ruleInputModel) {
        //建一个set集合（利用set的不可重复性，判断questionTypeId的重复性）
        Set set = new HashSet();
        int totalSection = 0;

        //遍历RuleSectionModel
        for (RulePartInputModel part : ruleInputModel.getParts()) {
            for (RuleSectionModel section : part.getSections()) {
                set.add(section.getQuestionTypeId());
                totalSection++;
            }
        }

        return set.size() == totalSection;
    }
    //endregion

    //region 计算类别占比总数(0视为不区分占比)

    /**
     * 计算类别占比总数(0视为不区分占比)
     *
     * @param categoryList the category list
     * @return the boolean
     */
    public BigDecimal checkCategoryPercent(Map<String, BigDecimal> categoryList) {

        BigDecimal totaPercent = BigDecimal.ZERO;

        for (Map.Entry<String, BigDecimal> map : categoryList.entrySet()) {
            totaPercent = totaPercent.add(map.getValue());
        }

        //value必须为1
        return totaPercent;
    }
    //endregion

    //region 得到策略所有大题的应用占比结果

    /**
     * 得到策略所有大题的应用占比结果.
     *
     * @param ruleInputModel the rule input model
     * @return the scala
     */

    private String getScala(RuleInputModel ruleInputModel) {
        List<String> partList = new ArrayList<>();

        for (Map.Entry<String, BigDecimal> category : ruleInputModel.getQuestionCategories().entrySet()) {
            partList.add(String.valueOf(category.getValue().floatValue()));
        }

        return String.join(",", partList);
    }
    //endregion

    //region 根据类别名称替换成类别

    /**
     * 根据类别名称替换成类别.
     *
     * @param dbCategories   类别列表
     * @param userCategories 用户类别占比map
     * @return the map
     */

    private Map<QuestionCategory, BigDecimal> getCategoryInfoByPercent(List<QuestionCategory> dbCategories, Map<String, BigDecimal> userCategories) {
        Map<QuestionCategory, BigDecimal> newCategories = new LinkedHashMap<>();

        for (Map.Entry<String, BigDecimal> category : userCategories.entrySet()) {
            for (QuestionCategory questionCategory : dbCategories) {
                if (questionCategory.getQuestionCategoryName().equalsIgnoreCase(category.getKey()) ||
                        questionCategory.getQuestionCategoryID().equalsIgnoreCase(category.getKey())) {
                    newCategories.put(questionCategory, category.getValue());
                }
            }
        }

        return newCategories;
    }

    /**
     * 根据类别名称替换成类别id.
     *
     * @param dbCategories   类别列表
     * @param ruleInputModel 策略输入模型
     * @return the map
     */
    private void replaceCategoryInfo(List<QuestionCategory> dbCategories, RuleInputModel ruleInputModel) {
        Map<String, BigDecimal> newCategories = new LinkedHashMap<>();

        for (Map.Entry<String, BigDecimal> category : ruleInputModel.getQuestionCategories().entrySet()) {
            for (QuestionCategory questionCategory : dbCategories) {
                if (questionCategory.getQuestionCategoryName().equalsIgnoreCase(category.getKey()) ||
                        questionCategory.getQuestionCategoryID().equalsIgnoreCase(category.getKey())) {
                    newCategories.put(questionCategory.getQuestionCategoryID(), category.getValue());
                }
            }
        }

        ruleInputModel.setQuestionCategories(newCategories);
    }

    /**
     * 根据类别id串得到类别名称串.
     *
     * @param categories  类别列表
     * @param categoryIds 类别id串
     * @return the category name by ids
     */

    private String getCategoryNameByIds(List<QuestionCategory> categories, String categoryIds) {
        List<String> names = new ArrayList<>();

        for (String categoryId : categoryIds.split(",")) {
            for (QuestionCategory questionCategory : categories) {
                if (questionCategory.getQuestionCategoryID().equalsIgnoreCase(categoryId)) {
                    names.add(questionCategory.getQuestionCategoryName());
                }
            }
        }

        return String.join(",", names);
    }
    //endregion

    //region 策略装载验证

    /**
     * 策略装载验证.
     *
     * @param ruleInputModel the rule input model
     * @return the return model
     */

    private ReturnModel checkRuleInputModelForEdit(RuleInputModel ruleInputModel) {
        ReturnModel model = new ReturnModel();
        RuleSaveModel saveModel = ruleInputModel.copyToSave();

        BigDecimal totalScore = BigDecimal.ZERO;

        //得到全部类别列表
        List<QuestionCategory> categories = questionCategoryService.getEnabledList("");

        if (!checkSectionUniqueness(ruleInputModel)) {
            model.setCode(GlobalConst.DATA_EXIST);
            model.setMessage("策略模型中存在相同题型设置");
            return model;
        }

        //检验试题类型是否为null
        if (ruleInputModel.getQuestionCategories() == null || ruleInputModel.getQuestionCategories().isEmpty()) {
            model.setCode(GlobalConst.DATA_FORMAT_ERROR);
            model.setMessage("试题应用类别不能为空");
            return model;
        }

        //应用类别占比总数(0视为不区分占比)
        BigDecimal percentSum = checkCategoryPercent(ruleInputModel.getQuestionCategories());
        if (percentSum.compareTo(BigDecimal.ONE) != 0 && percentSum.compareTo(BigDecimal.ZERO) != 0) {
            model.setCode(GlobalConst.DATA_FORMAT_ERROR);
            model.setMessage("试题应用比例之和必须为1");
            return model;
        }

        //获取类别实体集合
        Map<QuestionCategory, BigDecimal> checkCategory = getCategoryInfoByPercent(categories, ruleInputModel.getQuestionCategories());
        if (checkCategory.size() != ruleInputModel.getQuestionCategories().size()) {
            model.setCode(GlobalConst.DATA_FORMAT_ERROR);
            model.setMessage("类别设置错误");
            return model;
        }

        //替换类别信息，用户可能在类别处输入汉字或者guid
        replaceCategoryInfo(categories, ruleInputModel);

        //根据试题数量查询模型获取指定条件下试题数量
        Map<String, QuestionCountFilterModel> checkQuestionCountMap = new HashMap<>();
        //检查模型列表
        List<SectionCategoryDifficultyCountModel> countModelList = new ArrayList<>();
        //必选和例题map，true表示必选
        Map<String, Boolean> itemMap = new LinkedHashMap<>();

        //遍历Part
        for (RulePartInputModel partModel : ruleInputModel.getParts()) {
            RulePartSaveModel partSaveModel = partModel.copyToSave();

            if (partModel.isListening()) {
                model.setCode(GlobalConst.DATA_FORMAT_ERROR);
                model.setMessage("暂不支持听力题");
                return model;
            }

            //遍历Section
            for (RuleSectionInputModel sectionModel : partModel.getSections()) {
                RuleSectionSaveModel sectionSaveModel = sectionModel.copyToSave();

                //检查题型
                QuestionType questionType = questionTypeService.getQuestionType(sectionModel.getQuestionTypeId());
                if (questionType.getQuestionInnerTypeID() == 14){
                    partSaveModel.setListening(true);
                }
                if (questionType == null) {
                    model.setCode(GlobalConst.DATA_FORMAT_ERROR);
                    model.setMessage("未获取到相应的题型");
                    return model;
                }

                sectionModel.setSectionTitle(questionType.getQuestionTypeName());

                //大题中不允许不设置难度类型
                if (sectionModel.getDifficultyList().isEmpty()) {
                    model.setCode(GlobalConst.DATA_FORMAT_ERROR);
                    model.setMessage("未设定题型抽题规则");
                    return model;
                }

                //同难度类别，相同子试题个数set
                Set<String> diffSubItem = new HashSet<>();

                //遍历难度
                for (RuleDifficultyModel difficultyModel : sectionModel.getDifficultyList()) {
                    if (difficultyModel.getScore().compareTo(BigDecimal.ZERO) == 0) {
                        model.setCode(GlobalConst.DATA_FORMAT_ERROR);
                        model.setMessage("试题分数不能为0");
                        return model;
                    }

                    //如果子试题个数大于0则视为需要设置子试题，如果此时试题分数无法整除子试题个数，则视为设置错误
                    if (difficultyModel.getSubItemCount() > 0 &&
                            difficultyModel.getScore().divideAndRemainder(new BigDecimal(difficultyModel.getSubItemCount()))[1].compareTo(BigDecimal.ZERO) != 0) {
                        model.setCode(GlobalConst.DATA_FORMAT_ERROR);
                        model.setMessage("试题分数无法被子题数量均分");
                        return model;
                    }

                    //获取难度范围
                    Map<Float, Float> difficulty = cn.com.open.itembank.common.Function.getDifficultyValue(DifficultyType.fromValue(difficultyModel.getType()));
                    if (difficulty.isEmpty()) {
                        model.setCode(GlobalConst.DATA_FORMAT_ERROR);
                        model.setMessage("难度类型设置错误");
                        return model;
                    }

                    //目前只允许填空题型设置判分模式和主观题搜索范围
                    if (questionType.getQuestionInnerTypeID() != ItemType.TEXTENTRY.getIndex()) {
                        //自动评阅
                        difficultyModel.setSubjective(false);
                        //搜索范围全部
                        difficultyModel.setSubjectiveSearchScope(SubjectiveSearchScopeType.ALL.getIndex());
                    }

                    QuestionCountFilterModel filterModel = getfilterModelForSectionAndDiff(ruleInputModel.getItemBankID(), sectionModel, difficultyModel);

                    //同难度类别，相同子试题个数只能存在一条
                    diffSubItem.add(String.format("%s-%s-%d", filterModel.getMinDifficulty(), filterModel.getMaxDifficulty(), difficultyModel.getSubItemCount()));

                    totalScore = totalScore.add(difficultyModel.getScore().multiply(new BigDecimal(difficultyModel.getCount())));

                    //设置单个应用类别，需要结合难度计算
                    List<RuleDifficultySaveModel> diffSaveList = setCategory(difficultyModel, sectionModel.getSectionTitle(), filterModel,
                            checkCategory, checkQuestionCountMap, countModelList, percentSum);

                    sectionSaveModel.getDifficultyList().addAll(diffSaveList);
                }

                if (diffSubItem.size() != sectionModel.getDifficultyList().size()) {
                    model.setCode(GlobalConst.DATA_FORMAT_ERROR);
                    model.setMessage("相同难度范围，相同子试题数量下只能存在一条策略规则");
                    return model;
                }

                setItemMap(sectionModel.getMustQuestion(), itemMap, true);
                setItemMap(sectionModel.getExampleQuestion(), itemMap, false);

                partSaveModel.getSections().add(sectionSaveModel);
            }

            saveModel.getParts().add(partSaveModel);
        }

        //设置例题和必选题数值
        boolean checkMust = setItemCount(itemMap, countModelList);
        if (!checkMust) {
            model.setCode(GlobalConst.DATA_FORMAT_ERROR);
            model.setMessage("必须题或者例题错误");
            return model;
        }

        //判断分值是否匹配
        if (totalScore.compareTo(ruleInputModel.getTotalScore()) != 0) {
            model.setCode(GlobalConst.DATA_FORMAT_ERROR);
            model.setMessage("总分不匹配");
            return model;
        }

        //传值给模型（filterModel） 返回一个题库的试题数量
        Map<String, Integer> getCountMap = itemService.getQuestionCountForRulesCheck(checkQuestionCountMap);

        fillResult(countModelList, getCountMap);

        //判断抽题数量和db数量是否符合要求
        ReturnModel checkModel = checkModelCount(countModelList);
        if (checkModel.getCode() != GlobalConst.SUCCESS) {
            return checkModel;
        }

        saveModel.getQuestionCategories().addAll(ruleInputModel.getQuestionCategories().keySet());
        model.setData(saveModel);
        //验证通过
        model.setCode(GlobalConst.SUCCESS);
        return model;
    }

    //endregion

    //region 根据策略大题模型和难度模型得到试题数量筛选模型

    /**
     * 根据策略大题模型和难度模型得到试题数量筛选模型.
     *
     * @param itemBankId      题库id
     * @param sectionModel    策略大题模型
     * @param difficultyModel 难度模型
     * @return the model for section and diff
     */

    private QuestionCountFilterModel getfilterModelForSectionAndDiff(String itemBankId, RuleSectionModel sectionModel,
                                                                     RuleDifficultyModel difficultyModel) {
        QuestionCountFilterModel filterModel = new QuestionCountFilterModel();

        filterModel.setItemBankId(itemBankId);
        //设置例题和必选题，用于排除数字
        List<String> mandatoryIds = sectionModel.getExampleQuestion();
        mandatoryIds.addAll(sectionModel.getMustQuestion());
        filterModel.setMandatoryIDs(String.join(",", mandatoryIds));

        //设置题型
        filterModel.setQuestionTypeID(sectionModel.getQuestionTypeId());
        Map<Float, Float> difficulty = Function.getDifficultyValue(DifficultyType.fromValue(difficultyModel.getType()));
        for (Map.Entry<Float, Float> map : difficulty.entrySet()) {
            filterModel.setMinDifficulty(map.getKey());
            filterModel.setMaxDifficulty(map.getValue());
        }

        //章节ID列表
        filterModel.setChapterIDs(String.join(",", difficultyModel.getChapterList()));
        //设置子试题个数
        filterModel.setFieldcount(difficultyModel.getSubItemCount());
        //主观题搜索范围(仅填空题可用：0：主观；1：客观；2：全部)
        filterModel.setSubjective(difficultyModel.getSubjectiveSearchScope());

        //其他2个默认
        filterModel.setMinDuration(difficultyModel.getDurationStart());
        filterModel.setMaxDuration(difficultyModel.getDurationEnd());

        return filterModel;
    }
    //endregion

    //region 校验策略

    /**
     * 校验策略.
     *
     * @param rule the rule
     * @return the return model
     */

    private ReturnModel checkRule(RuleSaveModel rule) {
        ReturnModel model = new ReturnModel();

        //得到全部类别列表
        List<QuestionCategory> categories = questionCategoryService.getEnabledList("");

        //根据试题数量查询模型获取指定条件下试题数量
        Map<String, QuestionCountFilterModel> checkQuestionCountMap = new HashMap<>();
        //检查模型列表
        List<SectionCategoryDifficultyCountModel> countModelList = new ArrayList<>();
        //必选和例题map，true表示必选
        Map<String, Boolean> itemMap = new LinkedHashMap<>();

        for (RulePartSaveModel partModel : rule.getParts()) {
            for (RuleSectionSaveModel sectionModel : partModel.getSections()) {

                for (RuleDifficultySaveModel difficultyModel : sectionModel.getDifficultyList()) {

                    QuestionCountFilterModel filter = getfilterModelForSectionAndDiff(rule.getItemBankID(), sectionModel, difficultyModel);

                    String index = UUID.randomUUID().toString();
                    filter.setQuestionCategoryIDs(difficultyModel.getCategory());

                    //根据类别列表和类别id得到类别名称
                    String categoryNames = getCategoryNameByIds(categories, difficultyModel.getCategory());

                    //查询模型
                    checkQuestionCountMap.put(index, filter);

                    SectionCategoryDifficultyCountModel countModel = new SectionCategoryDifficultyCountModel(index, filter.getQuestionTypeID(),
                            sectionModel.getSectionTitle(), difficultyModel.getCategory(), DifficultyType.fromValue(difficultyModel.getType()), difficultyModel.getCount(),
                            categoryNames, difficultyModel.getSubItemCount());

                    countModelList.add(countModel);

                }
                setItemMap(sectionModel.getMustQuestion(), itemMap, true);
                setItemMap(sectionModel.getExampleQuestion(), itemMap, false);
            }
        }

        //设置例题和必选题数值
        boolean checkMust = setItemCount(itemMap, countModelList);
        if (!checkMust) {
            model.setCode(GlobalConst.DATA_FORMAT_ERROR);
            model.setMessage("必须题或者例题错误");
            return model;
        }

        //传值给模型（filterModel） 返回一个题库的试题数量
        Map<String, Integer> getCountMap = itemService.getQuestionCountForRulesCheck(checkQuestionCountMap);

        fillResult(countModelList, getCountMap);

        //判断抽题数量和db数量是否符合要求
        ReturnModel checkModel = checkModelCount(countModelList);
        if (checkModel.getCode() != GlobalConst.SUCCESS) {
            return checkModel;
        }

        //验证通过
        model.setCode(GlobalConst.SUCCESS);
        model.setData(checkQuestionCountMap);
        return model;
    }
    //endregion

    //region 判断题库数量是否符合要求

    /**
     * 判断题库数量是否符合要求.
     *
     * @param countModelList the count model list
     * @return the return model
     */

    private ReturnModel checkModelCount(List<SectionCategoryDifficultyCountModel> countModelList) {
        ReturnModel model = new ReturnModel();
        StringBuilder errorMsg = new StringBuilder();
        boolean checkPass = true;
        String itemDesc = "题型[%s]的难度为[%s]的类别为[%s]";

        //遍历得到集合中的元素
        for (SectionCategoryDifficultyCountModel count : countModelList) {

            //判断用户所需数量+例题数量+必选题数量 是否符合数据库试题数量要求
            if (count.getDbCount() < count.getUserNeedCount() + count.getExampleCount() + count.getMandatoryCount()) {
                double num = count.getUserNeedCount() + count.getExampleCount() + count.getMandatoryCount() - count.getDbCount();
                checkPass = false;
                errorMsg.append(String.format(itemDesc, count.getQuestionTypeName(), count.getDifficultyType(), count.getCategoryNames()) +
                        "的试题需求量超出题库" + num + "题。");
            }

            //通过验证返回
            if (checkPass) {
                model.setCode(GlobalConst.SUCCESS);
            } else {
                model.setCode(GlobalConst.DATA_FORMAT_ERROR);
                model.setMessage(errorMsg.toString());
            }
        }

        return model;
    }
    //endregion

    //region 设置必须题或者例题map

    /**
     * 设置必须题或者例题map.
     *
     * @param itemIds 试题列表
     * @param itemMap 结果map
     * @param isMast  是否必选，例题false
     */

    private void setItemMap(List<String> itemIds, Map<String, Boolean> itemMap, boolean isMast) {
        for (String itemId : itemIds) {
            itemMap.put(itemId, isMast);
        }
    }
    //endregion

    //region 填充结果数据

    /**
     * 填充结果数据.
     *
     * @param modelList   the model list
     * @param getCountMap the get count map
     */

    private void fillResult(List<SectionCategoryDifficultyCountModel> modelList, Map<String, Integer> getCountMap) {
        for (SectionCategoryDifficultyCountModel model : modelList) {
            if (getCountMap.containsKey(model.getIndex())){
                int dbCount = getCountMap.get(model.getIndex());
                model.setDbCount(dbCount > 0 ? dbCount : 0);
            }
        }
    }
    //endregion

    //region 设置类别数据

    /**
     * 设置类别数据.
     *
     * @param difficultyModel 难度模型
     * @param sectionTitle    大题名称
     * @param filterModel     基础筛选对象
     * @param categorys       类别map
     * @param map             题库数量筛选map
     * @param modelList       结果列表
     * @param percentSum      占比总数
     */

    private List<RuleDifficultySaveModel> setCategory(RuleDifficultyModel difficultyModel, String sectionTitle, QuestionCountFilterModel filterModel,
                                                      Map<QuestionCategory, BigDecimal> categorys, Map<String, QuestionCountFilterModel> map,
                                                      List<SectionCategoryDifficultyCountModel> modelList, BigDecimal percentSum) {
        List<RuleDifficultySaveModel> diffSaveList = new ArrayList<>();

        if (percentSum.compareTo(BigDecimal.ZERO) == 0) {
            //不区分占比
            RuleDifficultySaveModel difficultySaveModel = difficultyModel.copyToSave();
            QuestionCountFilterModel filter = filterModel.copy();

            List<String> categoryInfo = getCategoryInfoByPercent(categorys.keySet());

            String categoryIds = categoryInfo.get(0);
            String categoryNames = categoryInfo.get(1);
            String index = UUID.randomUUID().toString();

            filter.setQuestionCategoryIDs(categoryIds);

            SectionCategoryDifficultyCountModel model = new SectionCategoryDifficultyCountModel(index, filter.getQuestionTypeID(),
                    sectionTitle, categoryIds, DifficultyType.fromValue(difficultyModel.getType()), difficultyModel.getCount(), categoryNames, difficultyModel.getSubItemCount());

            difficultySaveModel.setCategory(categoryIds);
            difficultySaveModel.setRealCount(difficultyModel.getCount());

            //查询模型
            map.put(index, filter);
            modelList.add(model);

            diffSaveList.add(difficultySaveModel);
        } else {
            //区分占比
            //类别数量
            int categoryCount = categorys.size();
            //当前类别索引
            int curCategoryIdx = 1;
            //所有已经使用的取整
            double diffUsedCount = 0;

            for (Map.Entry<QuestionCategory, BigDecimal> category : categorys.entrySet()) {

                RuleDifficultySaveModel difficultySaveModel = difficultyModel.copyToSave();
                QuestionCountFilterModel filter = filterModel.copy();

                String categoryIds = category.getKey().getQuestionCategoryID();
                String categoryNames = category.getKey().getQuestionCategoryName();
                String index = UUID.randomUUID().toString();
                BigDecimal realCount = category.getValue().multiply(new BigDecimal(difficultyModel.getCount()));

                //默认向上取整
                double count = Math.ceil(realCount.doubleValue());
                if (curCategoryIdx == categoryCount) {
                    //最后一个占比，用难度题目数字-所有已经取整
                    count = difficultyModel.getCount() - diffUsedCount;
                } else {
                    diffUsedCount += count;
                }

                filter.setQuestionCategoryIDs(categoryIds);

                //向上取整
                SectionCategoryDifficultyCountModel model = new SectionCategoryDifficultyCountModel(index, filter.getQuestionTypeID(),
                        sectionTitle, categoryIds, DifficultyType.fromValue(difficultyModel.getType()),
                        count, categoryNames, difficultyModel.getSubItemCount());

                difficultySaveModel.setCategory(categoryIds);
                //真实值
                difficultySaveModel.setRealCount(realCount.floatValue());
                //抽题数量--非最后一个向上取整，否则减法得到
                difficultySaveModel.setCount((int) count);

                //查询模型
                map.put(index, filter);
                modelList.add(model);
                diffSaveList.add(difficultySaveModel);

                curCategoryIdx++;
            }
        }

        return diffSaveList;
    }
    //endregion

    //region 获得试题应用类别信息列表

    /**
     * 获得试题应用类别信息列表.
     *
     * @param categorys the categorys
     * @return the category info
     */

    private List<String> getCategoryInfoByPercent(Set<QuestionCategory> categorys) {
        List<String> categoryIds = new ArrayList<>();
        List<String> categoryNames = new ArrayList<>();
        for (QuestionCategory category : categorys) {
            categoryIds.add(category.getQuestionCategoryID());
            categoryNames.add(category.getQuestionCategoryName());
        }

        List<String> result = new ArrayList<>();
        result.add(String.join(",", categoryIds));
        result.add(String.join(",", categoryNames));

        return result;
    }
    //endregion

    //region 设置必选和例题数量

    /**
     * 设置必选和例题数量.
     *
     * @param itemMap        必选和例题集合
     * @param countModelList 结果数据
     * @return the item count
     */

    private boolean setItemCount(Map<String, Boolean> itemMap, List<SectionCategoryDifficultyCountModel> countModelList) {

        if (itemMap.isEmpty()) {
            return true;
        }

        List<RuleCheckQuestionInfo> model = itemService.getQuestionInfoForRuleCheck(itemMap);

        if (model.size() != itemMap.size()) {
            return false;
        }

        for (SectionCategoryDifficultyCountModel result : countModelList) {
            int mustCount = getItemCount(model, result.getQuestionTypeId(), result.getDifficultyType(), result.getCategoryIds(),
                    true, result.getSubItemCount());
            int exampleCount = getItemCount(model, result.getQuestionTypeId(), result.getDifficultyType(), result.getCategoryIds(),
                    false, result.getSubItemCount());
            result.setMandatoryCount(mustCount);
            result.setExampleCount(exampleCount);
        }

        return true;
    }
    //endregion

    //region 遍历必选和例题模型集合得到匹配数量

    /**
     * 遍历必选和例题模型集合得到匹配数量.
     *
     * @param items          必选和例题模型集合
     * @param questionTypeId 要匹配的题型
     * @param difficultyType 要匹配的难度
     * @param categoryIds    要匹配的类别串
     * @param isMust         是否必选
     * @param subItemCount   子试题数量
     * @return the item count
     */

    private int getItemCount(List<RuleCheckQuestionInfo> items, String questionTypeId, DifficultyType difficultyType,
                             String categoryIds, boolean isMust, int subItemCount) {
        int result = 0;

        for (RuleCheckQuestionInfo item : items) {
            boolean diffCheck = true;

            if (difficultyType != DifficultyType.ALL) {
                diffCheck = item.getDifficultyType().equals(difficultyType);
            }

            if (diffCheck && item.getQuestionTypeId().equalsIgnoreCase(questionTypeId) && item.isMust() == isMust
                    && categoryIds.indexOf(item.getQuestionCategoryId()) >= 0 && item.getSubItemCount() == subItemCount) {
                result++;
            }
        }

        return result;
    }
    //endregion

    //region 根据appKey，题库id，验证权限

    /**
     * 根据appKey，题库id，验证权限.
     *
     * @param appKey     appKey
     * @param itemBankId 题库id
     * @return the return model
     */

    private ReturnModel basicCheckForItemBank(String appKey, String itemBankId) {

        //权限验证
        ReturnModel model = checkItemBank(itemBankId);

        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        //确认除非非限定appid否则只能修改appid
        if (!itemBankService.hasPermission(itemBankId, appKey)) {
            model.setCode(GlobalConst.NO_PERMISSION);
            model.setMessage(noPermission);
            return model;
        }

        //验证通过返回
        model.setCode(GlobalConst.SUCCESS);
        return model;
    }

    /**
     * 根据appKey，题库id，验证权限.
     *
     * @param appKey     appKey
     * @param itemBankId 题库id
     * @param id         策略id
     * @return the return model
     */

    private ReturnModel basicCheck(String appKey, String itemBankId, String id) {

        //权限验证
        ReturnModel model = checkRules(itemBankId, id);

        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        //确认除非非限定appId否则只能修改appId
        if (StringUtils.isEmpty(appKey) || !itemBankService.hasPermission(itemBankId, appKey)) {
            model.setCode(GlobalConst.NO_PERMISSION);
            model.setMessage(noPermission);
            return model;
        }

        //验证通过返回
        model.setCode(GlobalConst.SUCCESS);
        return model;
    }
    //endregion

    //region 根据题库id,策略id检测题库和策略的合法性

    /**
     * 根据题库id,策略id检测题库和策略的合法性
     *
     * @param itemBankId 题库id
     * @param id         策略id
     * @return the return model
     */

    private ReturnModel checkRules(String itemBankId, String id) {
        ReturnModel model = checkItemBank(itemBankId);

        if (model.getCode() != GlobalConst.SUCCESS) {
            return model;
        }

        //Id合法性
        if (StringUtils.isEmpty(id)) {
            model.setCode(GlobalConst.INCOMPLETE_DATA);
            model.setMessage(incompleteData);
            return model;
        }

        Rules rules = rulesMapper.getRulesById(id);
        if (rules == null) {
            model.setCode(GlobalConst.ID_NOT_EXIST);
            model.setData(null);
            model.setMessage("策略不存在");
            return model;
        }

        //验证通过返回
        model.setData(rules);
        model.setCode(GlobalConst.SUCCESS);
        return model;
    }

    /**
     * 根据题库id检测题库的合法性
     *
     * @param itemBankId 题库id
     * @return the return model
     */

    private ReturnModel checkItemBank(String itemBankId) {
        ReturnModel model = new ReturnModel();

        //itemBankId合法性
        if (StringUtil.isEmpty(itemBankId)) {
            model.setCode(GlobalConst.INCOMPLETE_DATA);
            model.setMessage(incompleteData);
            return model;
        }

        //题库合法性
        CourseItemBank itemBank = itemBankService.getCourseItemBankInfo(itemBankId);
        if (itemBank == null) {
            model.setCode(GlobalConst.COURSE_NOT_EXIST);
            model.setMessage(itembankIdNotExist);
            return model;
        }

        //验证通过返回
        model.setCode(GlobalConst.SUCCESS);
        model.setData(itemBank);
        return model;
    }
    //endregion

    //endregion
}

